using System;
using System.Collections.Generic;
using System.Text;
using Barrage.Global.Vertex;
using SlimDX;
using SlimDX.Direct3D9;
using Barrage.Global;

namespace Barrage.Meshes.Text
{
    /// <summary>
    /// List of horizontal text alignment modes
    /// </summary>
    public enum TextAlignment
    {
        /// <summary>
        /// Left horizontal alignment
        /// </summary>
        Left,
        /// <summary>
        /// Right horizontal alignment
        /// </summary>
        Right,
        /// <summary>
        /// Center horizontal alignment
        /// </summary>
        Center
    }
    /// <summary>
    /// List of vertical text alignment modes
    /// </summary>
    public enum VerticalAlignment
    {
        /// <summary>
        /// Top vertical alignment
        /// </summary>
        Top,
        /// <summary>
        /// Bottom vertical alignment
        /// </summary>
        Bottom,
        /// <summary>
        /// Center vertical alignment
        /// </summary>
        Center
    }
    /// <summary>
    /// Structure that holds information of the result of the initial text placement computations (one line each structure)
    /// </summary>
    struct TextBoxDrawResults
    {
        /// <summary>
        /// Text to be written on this line
        /// </summary>
        public string text;
        /// <summary>
        /// Width in pixels of this line
        /// </summary>
        public int pxWidth;
       
        public TextBoxDrawResults(string text, int pxWidth)
        {
            this.text = text;
            this.pxWidth = pxWidth;
        }
    }
    /// <summary>
    /// Structure that specifies drawing parameters for a TextBoxMesh
    /// </summary>
    public struct TextBoxDrawProperties
    {
        /// <summary>
        /// Horizontal text alignment
        /// </summary>
        public TextAlignment Alignment;
        /// <summary>
        /// Vertical text alignment
        /// </summary>
        public VerticalAlignment VerticalAlignment;
        
        /// <summary>
        /// Size of the text box
        /// </summary>
        public Vector2 Size;
        /// <summary>
        /// Offset of the text box
        /// </summary>
        public Vector2 Offset;
        /// <summary>
        /// Text scale
        /// </summary>
        public float Scale;
        /// <summary>
        /// Constructor. Inits values
        /// </summary>
        /// <param name="alignment">Horizontal text alignment</param>
        /// <param name="Size">Size of the text box</param>
        /// <param name="scale">Text scale</param>
        /// <param name="vAlign">Vertical text alignment</param>
        /// <param name="offset">Offset of the text box</param>
        public TextBoxDrawProperties(TextAlignment alignment, Vector2 Size,float scale,VerticalAlignment vAlign,Vector2 offset)
        {
            Alignment = alignment;
            this.Size = Size;
            Scale = scale;
            VerticalAlignment = vAlign;
            this.Offset = offset;
        }
    }
    /// <summary>
    /// TextBoxMesh is like TextMesh, but it renders text trying to hold the results inside a box. Useful for buttons etc.
    /// </summary>
    public class TextBoxMesh : GuiMesh
    {
        const int MAX_TEXT_SIZE = 1024;
        BitmapFont font;

        bool dirty = false;

        /// <summary>
        /// Constructor. Inits values
        /// </summary>
        /// <param name="font">BitmapFont used to render the text</param>
        /// <param name="properties">Drawing properties</param>
        public TextBoxMesh(BitmapFont font,TextBoxDrawProperties properties) 
        {
            this.font = font;
            drawProperties = properties;
            materials[0].Texture = font.Texture;
            if (indexBuffer==null)
                initializeIndexBuffer();
            initializeBuffer();
            Engine.RegisterDisposeDefaultPoolHandler(new EngineDisposeHandler(DisposeTextBuffer));
            Engine.RegisterRecreateDefaultPoolHandler(new EngineDisposeHandler(initializeBuffer));
        }

        string text;
        /// <summary>
        /// Gets / Sets text to be rendered
        /// </summary>
        public string Text { get { return text; } set { text = value; dirty = true; } }

        TextBoxDrawProperties drawProperties;

        VertexPositionUV[] textData = new VertexPositionUV[6*MAX_TEXT_SIZE];


        static IndexBuffer indexBuffer=null;
        VertexBuffer textBuffer;

        int charCount = 0;

        static void initializeIndexBuffer()
        {
            indexBuffer = new IndexBuffer(Engine.Device, 4 * 6 * MAX_TEXT_SIZE, Usage.WriteOnly, Pool.Managed, false);
            DataStream stream = indexBuffer.Lock(0, 0, LockFlags.None);
            int[] indices = new int[MAX_TEXT_SIZE * 6];
            for (int i = 0; i < MAX_TEXT_SIZE; i++)
            {
                int baseIndex = i * 6;
                int vertexIndex = i * 4;
                indices[baseIndex] = vertexIndex;
                indices[baseIndex + 1] = vertexIndex + 1;
                indices[baseIndex + 2] = vertexIndex + 2;
                indices[baseIndex + 3] = vertexIndex + 2;
                indices[baseIndex + 4] = vertexIndex + 3;
                indices[baseIndex + 5] = vertexIndex + 1;
            };
            stream.WriteRange(indices);
            indexBuffer.Unlock();
            Engine.RegisterDisposeHandler(new EngineDisposeHandler(disposeIndexBuffer));
        }

        static void disposeIndexBuffer()
        {
            if (indexBuffer != null)
                indexBuffer.Dispose();
            indexBuffer = null;
        }

        void initializeBuffer()
        {
            textBuffer = new VertexBuffer(Engine.Device, VertexPositionUV.SizeBytes * 4 * 6 * MAX_TEXT_SIZE, Usage.WriteOnly | Usage.Dynamic, VertexPositionUV.Format, Pool.Default);            
        }

        void updateData()
        {
            charCount = 0;
            
            List<TextBoxDrawResults> results = new List<TextBoxDrawResults>();
            string str = text;
            while (str.Length != 0)
            {
                int nChars;
                int pxWidth;
                
                font.CountCharWidth((int)(drawProperties.Size.X / drawProperties.Scale), str, out nChars, out pxWidth);
                results.Add(new TextBoxDrawResults(str.Substring(0, nChars), pxWidth)); 
                str = str.Substring(nChars);
                
                
            }

            Vector2 vAt = new Vector2(-drawProperties.Size.X / 2f, drawProperties.Size.Y / 2f);

            float maxLines = (drawProperties.Size.Y / (font.FontInfo.LineHeight * drawProperties.Scale));
            
            int i=0;
            foreach (TextBoxDrawResults tr in results)
            {
                if (i > maxLines) return;   
                switch (drawProperties.Alignment)
                {
                    case TextAlignment.Left:

                        vAt.X = -drawProperties.Size.X/2f;
                        break;
                    case TextAlignment.Center:
                        vAt.X = -drawProperties.Size.X / 2f +((drawProperties.Size.X - tr.pxWidth * drawProperties.Scale) / 2);
                        break;
                    case TextAlignment.Right:
                        vAt.X = -drawProperties.Size.X / 2f + (drawProperties.Size.X - tr.pxWidth * drawProperties.Scale);
                        break;
                }

                switch (drawProperties.VerticalAlignment)
                {
                    case VerticalAlignment.Top:
                        vAt.Y = drawProperties.Size.Y / 2f - i * font.FontInfo.LineHeight * drawProperties.Scale;
                        break;
                    case VerticalAlignment.Center:
                        vAt.Y = (font.FontInfo.LineHeight * (Math.Min(results.Count,maxLines) / 2f - 0.5f) - font.FontInfo.LineHeight * i) * drawProperties.Scale;
                        break;
                    case VerticalAlignment.Bottom:
                        vAt.Y = -drawProperties.Size.Y / 2f + i * font.FontInfo.LineHeight * drawProperties.Scale;
                        break;
                }
                addData(vAt, tr.text);
                i++;
            }
        }
        
        void addData(Vector2 vAt,string substr)
        {
            Vector2 vOrigin = new Vector2(0, 0);
            
            // draw each character in the string
            foreach (char c in substr)
            {
                int baseIndex = 4 * charCount;
                if (!font.CharInfo.ContainsKey(c))
                {
                    continue;
                }

                CharInfo cinfo = font.CharInfo[c];

                // draw the glyph
                vAt.X += cinfo.XOffset * drawProperties.Scale;
                if (cinfo.Width != 0 && cinfo.Height != 0)
                {
                    textData[baseIndex + 2].UV = new Vector2(cinfo.X / (float)font.FontInfo.ScaleW, cinfo.Y / (float)font.FontInfo.ScaleH);
                    textData[baseIndex + 3].UV = new Vector2((cinfo.X + cinfo.Width) / (float)font.FontInfo.ScaleW, cinfo.Y / (float)font.FontInfo.ScaleH);
                    textData[baseIndex].UV = new Vector2(cinfo.X / (float)font.FontInfo.ScaleW, (cinfo.Y + cinfo.Height) / (float)font.FontInfo.ScaleH);
                    textData[baseIndex + 1].UV = new Vector2((cinfo.X + cinfo.Width) / (float)font.FontInfo.ScaleW, (cinfo.Y + cinfo.Height) / (float)font.FontInfo.ScaleH);

                    textData[baseIndex].Position = new Vector3(vAt.X, vAt.Y - cinfo.Height * drawProperties.Scale / 2f, 0);
                    textData[baseIndex + 1].Position = new Vector3(vAt.X + cinfo.Width * drawProperties.Scale, vAt.Y - cinfo.Height * drawProperties.Scale / 2f, 0);
                    textData[baseIndex + 2].Position = new Vector3(vAt.X, vAt.Y + cinfo.Height * drawProperties.Scale / 2f, 0);
                    textData[baseIndex + 3].Position = new Vector3(vAt.X + cinfo.Width * drawProperties.Scale, vAt.Y + cinfo.Height * drawProperties.Scale / 2f, 0);

                    textData[baseIndex].Position += new Vector3(drawProperties.Offset,0);
                    textData[baseIndex + 1].Position += new Vector3(drawProperties.Offset, 0);
                    textData[baseIndex + 2].Position += new Vector3(drawProperties.Offset, 0);
                    textData[baseIndex + 3].Position += new Vector3(drawProperties.Offset, 0);
                }
                // Advance the pen to the next drawing position
                vAt.X += cinfo.XAdvance * drawProperties.Scale - cinfo.XOffset * drawProperties.Scale;

                charCount++;

            }

            DataStream stream = textBuffer.Lock(0, 6 * text.Length * VertexPositionUV.SizeBytes, LockFlags.Discard);
            stream.WriteRange(textData, 0, 6 * text.Length);
            textBuffer.Unlock();

        }

        /// <summary>
        /// \internal Renders the text box
        /// </summary>
        /// <param name="item">Item rendering this mesh</param>
        public override void Render(Item item)
        {
            shader.BeginRender(item, materials[0]);
            
            if (dirty) { updateData(); }
            
            Engine.Device.VertexFormat = VertexPositionUV.Format;
            Engine.Device.Indices = indexBuffer;
            Engine.Device.SetStreamSource(0, textBuffer, 0, VertexPositionUV.SizeBytes);
            int passNumber = shader.PassNumber(materials[0]);
            for (int j = 0; j < passNumber; j++)
            {
                shader.BeginPass(j);
                Engine.Device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 4 * charCount, 0, 2 * charCount);
                shader.EndPass();
            }
            shader.EndRender();
        }

        /// <summary>
        /// \internal Clear resources
        /// </summary>
        public override void Dispose()
        {
            Engine.UnregisterDisposeDefaultPoolHandler(new EngineDisposeHandler(DisposeTextBuffer));
            Engine.UnregisterRecreateDefaultPoolHandler(new EngineDisposeHandler(initializeBuffer));
            DisposeTextBuffer();
            font = null;
            textData = null;

            base.Dispose();
        }
        void DisposeTextBuffer()
        {
            if (textBuffer != null)
                textBuffer.Dispose();
            textBuffer = null;
            
        }
    }
}
